//
//  TimedMachine.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 31/03/2020.
//  Copyright © 2020 Thomas Harte. All rights reserved.
//

#pragma once

#include "ClockReceiver/ClockReceiver.hpp"
#include "ClockReceiver/TimeTypes.hpp"

#include "AudioProducer.hpp"

#include <cmath>

namespace MachineTypes {

/*!
	A timed machine is any which requires the owner to provide time-based updates,
	i.e. run_for(<some number of seconds>)-type calls.

*/
class TimedMachine {
public:
	/// Runs the machine for @c duration seconds.
	virtual void run_for(const Time::Seconds duration) {
		const double cycles = (duration * clock_rate_ * speed_multiplier_) + clock_conversion_error_;
		clock_conversion_error_ = std::fmod(cycles, 1.0);
		run_for(Cycles(int(cycles)));
	}

	/*!
		Sets a speed multiplier to apply to this machine; e.g. a multiplier of 1.5 will cause the
		emulated machine to run 50% faster than a real machine. This speed-up is an emulation
		fiction: it will apply across the system, including to the CRT.
	*/
	virtual void set_speed_multiplier(const double multiplier) {
		if(speed_multiplier_ == multiplier) {
			return;
		}

		speed_multiplier_ = multiplier;

		auto audio_producer = dynamic_cast<AudioProducer *>(this);
		if(!audio_producer) return;

		auto speaker = audio_producer->get_speaker();
		if(speaker) {
			speaker->set_input_rate_multiplier(float(multiplier));
		}
	}

	/*!
		@returns The current speed multiplier.
	*/
	virtual double get_speed_multiplier() const {
		return speed_multiplier_;
	}

	/// @returns The confidence that this machine is running content it understands.
	virtual float get_confidence() { return 0.5f; }
	virtual std::string debug_type() { return ""; }

	struct Output {
		static constexpr int Video = 1 << 0;
		static constexpr int Audio = 1 << 1;

		static constexpr int All = Video | Audio;
	};
	/// Ensures all locally-buffered output is posted onward for the types of output indicated
	/// by the bitfield argument, which is comprised of flags from the namespace @c Output.
	virtual void flush_output(int) {}

protected:
	/// Runs the machine for @c cycles.
	virtual void run_for(const Cycles) = 0;

	/// Sets this machine's clock rate.
	void set_clock_rate(const double clock_rate) {
		clock_rate_ = clock_rate;
	}

	/// Gets this machine's clock rate.
	double get_clock_rate() const {
		return clock_rate_;
	}

private:
	// Give the ScanProducer access to this machine's clock rate.
	friend class ScanProducer;

	double clock_rate_ = 1.0;
	double clock_conversion_error_ = 0.0;
	double speed_multiplier_ = 1.0;
};

}
